﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;

namespace MassIntellect.WorkIt
{
	public sealed class SerializeScope : IDisposable
	{
		private class DisposableStub : IDisposable
		{
			public void Dispose()
			{
			}
		}

		private static long Counter = new Random((int)DateTime.Now.Ticks).Next();
		private Dictionary<long, object> Objs
			= new Dictionary<long, object>();
		private Dictionary<object, long> IDs
			= new Dictionary<object, long>();
		private Dictionary<object, Action<object, long>> refHandlers
			= new Dictionary<object, Action<object, long>>();
		private DictionaryList<long, Action<object>> Lazies
			= new DictionaryList<long, Action<object>>();

		private readonly static Stack<SerializeScope> stack
			= new Stack<SerializeScope>();

		private SerializeScope()
		{

		}

		public static IDisposable Create()
		{
			stack.Push(new SerializeScope());
			return stack.Peek();
		}

		public static void ExecuteInNewContext(Action act)
		{
			if (act != null)
				using (Create())
					act();
		}

		public static IDisposable CreateIfNeeded()
		{
			return stack.Count > 0 ? new DisposableStub() : Create();
		}

		private static long GetID()
		{
			return ++Counter;
		}

		private long add(object obj, Action<object, long> refHandle)
		{
			Action<object, long> ref2;
			long res = 0;
			if (obj != null)
				if (!IDs.TryGetValue(obj, out res))
					AttachObj(res = GetID(), obj, refHandle);
				else if (refHandlers.TryGetValue(obj, out ref2))
					ref2(obj, res);
				else if (refHandle != null)
				{
					refHandlers.Add(obj, refHandle);
					refHandle(obj, res);
				}
			return res;
		}

		public static long Add(object obj)
		{
			return stack.Count > 0 ? stack.Peek().add(obj, null) : 0;
		}

		/// <summary>
		/// Добавить объект к контексту.
		/// </summary>
		/// <param name="obj"></param>
		/// <param name="refHandle">Действие в случае попытки повторного добавления</param>
		/// <returns></returns>
		public static long Add(object obj, Action<object, long> refHandle)
		{
			return stack.Count > 0 ? stack.Peek().add(obj, refHandle) : 0;
		}

		public static bool IsPresentObject(object obj)
		{
			return stack.Count > 0 ? stack.Peek().IDs.ContainsKey(obj) : false;
		}

		public static bool IsPresentID(long key)
		{
			return stack.Count > 0 ? stack.Peek().Objs.ContainsKey(key) : false;
		}

		public void add(long id, object obj)
		{
			object o;
			long l;
			if (obj != null
				&& (Objs.TryGetValue(id, out o) == IDs.TryGetValue(obj, out l))
				&& (o == null
					|| (obj == o
						&& id == l).IfFalse(() => new Exception("Ошибка контекста сериализации. Попытка добавить объект с существующи id"))))
				AttachObj(id, obj, null);
		}

		public static bool Add(long id, object obj)
		{
			if (stack.Count > 0)
			{
				stack.Peek().add(id, obj);
				return true;
			}
			return false;
		}

		private void AttachObj(long id, object obj, Action<object, long> refHandle)
		{
			Objs.Add(id, obj);
			IDs.Add(obj, id);
			if (refHandle != null)
				refHandlers.Add(obj, refHandle);
			foreach (var lazy in Lazies[id])
				lazy(obj);
			Lazies.Remove(id);
		}

		private void getOrSuspend(long id, Action<object> lazy)
		{
			object o;
			if (id != 0)
				if (Objs.TryGetValue(id, out o))
					lazy(o);
				else
					Lazies.Add(id, lazy);
		}

		public static bool GetOrSuspend(long id, Action<object> lazy)
		{
			if (stack.Count > 0)
			{
				stack.Peek().getOrSuspend(id, lazy);
				return true;
			}
			return false;
		}

		public static bool HasScope
		{
			get { return stack.Count > 0; }
		}

		public void Dispose()
		{
			stack.Pop();
			Lazies = null;
			IDs = null;
			Objs = null;
			refHandlers = null;
		}
	}
}